package guiserver;

import com.sun.org.apache.xpath.internal.operations.Equals;
import guicommon.controls.Control;
import guicommon.controls.Window;
import guicommon.controls.handlers.KeyEventArgs;
import guicommon.controls.handlers.MouseEventArgs;
import guicommon.gui.ControlMessage;
import guicommon.gui.UpdateMessage;
import java.awt.Graphics;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Hashtable;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class ControlsManager {

    private static ControlsManager instance;

     //Примитив синхронизации
    private Lock lock;

    public static ControlsManager GetInstance() {
        if (instance == null) {
            instance = new ControlsManager();            
        }
        return instance;
    }

    private JPanel board;

    private ControlsManager() {
        mapOwnerToChildren = new Hashtable();
        mapIdToControl = new Hashtable();
        mapWindowToOrder = new Hashtable();
        mapControlToClient = new Hashtable();
        mapWindowToFocusedControl = new Hashtable();
        mapClientControlPairToGlobalId = new Hashtable();
        lock = new ReentrantLock();
    }

    private class ClientControlPair {

        private SingleClient client;

        private String id;

        public SingleClient GetClient() {
            return client;
        }

        public String GetId() {
            return id;
        }

        public ClientControlPair(SingleClient client, String id) {
            this.client = client;
            this.id = id;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + id.hashCode();
            result = prime * result + client.hashCode();
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            ClientControlPair other = (ClientControlPair) obj;
            if (!id.equals(other.id))
                return false;
            if (client != other.client)
                return false;
            return true;
        }
    }

    private Hashtable<Window, Integer> mapWindowToOrder;

    private Hashtable<Control, List<Control>> mapOwnerToChildren;

    private Hashtable<String, Control> mapIdToControl;

    private Hashtable<ClientControlPair, String> mapClientControlPairToGlobalId;

    private Hashtable<Control, SingleClient> mapControlToClient;

    private Hashtable<Window, Control> mapWindowToFocusedControl;

    public void InitJPanel(JPanel control) {
        this.board = control;
    }

    public Control RegisterControl(SingleClient client, ControlMessage msg) {
        lock.lock();
        Control control = msg.ToControl();
        mapControlToClient.put(control, client);
        String id = msg.GetBodyField(ControlMessage.ID);
        String globalId = control.GetID();
        mapIdToControl.put(globalId, control);
        mapClientControlPairToGlobalId.put(new ClientControlPair(client, id), globalId);
        String ownerId = msg.GetBodyField(ControlMessage.OWNER);
        if (ownerId == null) {
            control.SetOwner(null);
        } else {
            ClientControlPair ownerPair = new ClientControlPair(client, ownerId);
            ownerId = mapClientControlPairToGlobalId.get(ownerPair);
            control.SetOwner(mapIdToControl.get(ownerId));
        }
        
        if (control.getClass() == Window.class) {
            //board.add(new JFrame());
            mapOwnerToChildren.put(control, new ArrayList());
            IncrementOrder();
            mapWindowToOrder.put((Window) control, 0);
        } else if (control.IsContainer()){
            mapOwnerToChildren.get(control.GetOwner()).add(control);
            mapOwnerToChildren.put(control, new ArrayList());
        } else {
            mapOwnerToChildren.get(control.GetOwner()).add(control);
        }
        lock.unlock();
        board.updateUI();
        return control;
    }

    private Window[] GetOrderedWindows() {
        Window[] windows = new Window[mapWindowToOrder.size()];
        windows = mapWindowToOrder.keySet().toArray(windows);
        Arrays.sort(windows, new Comparator<Window>() {
            
            @Override
            public int compare(Window o1, Window o2) {
                if (mapWindowToOrder.get(o1) < mapWindowToOrder.get(o2)) {
                    return -1;
                }
                return 1;
            }

        });
        return windows;
    }

    public void Redraw(Graphics g) {
        lock.lock();
        Window[] windows = GetOrderedWindows();
        for (int i = windows.length - 1; i > -1; i--) {
            Window window = windows[i];
            Graphics windowGraphics = window.GetGraphics();
            RedrawChildContainers(windowGraphics, window);
            window.Draw(g, board);
        }
        lock.unlock();
    }

    private void RedrawChildContainers(Graphics g, Control control) {
        //Отрисовываем сначала все контейнеры
        for (Control childContainer : mapOwnerToChildren.get(control)) {
            if (childContainer.IsContainer()) {
                Graphics windowGraphics = childContainer.GetGraphics();
                //отрисовываем дочерние контейнеры
                RedrawChildContainers(g, childContainer);
                childContainer.Draw(g, board);
            }
        }
        //потом обычные дочерние элементы
        for (Control child : mapOwnerToChildren.get(control)) {
            if (!child.IsContainer()) {
                child.Draw(g, board);
            }
        }
    }

    public void OnMouseDownHandler(int x, int y) {
        
    }

    public void OnMouseUpHandler(int x, int y) {
        
    }

    public void OnMouseMovedHandler(int x, int y) {

    }

    public void OnMouseClickedHandler(int x, int y) {
        Window clickedWindow = GetWindow(x, y);
        Control selectedControl = GetControl(x, y);
        if (selectedControl != null) {
            mapControlToClient.get(selectedControl).RegisterEvent(
                    selectedControl, new MouseEventArgs(x, y));
        }
        if (clickedWindow != null) {
            mapWindowToFocusedControl.put(clickedWindow, selectedControl);
        }
    }

    private Window GetWindow(int x, int y) {
        for (Window window : GetOrderedWindows()) {
            if (window.Contains(x, y)) {
                if (mapWindowToOrder.get(window) != 0) {
                    mapWindowToOrder.remove(window);
                    IncrementOrder();
                    mapWindowToOrder.put(window, 0);
                }
                return window;
            }
        }
        return null;
    }

    private Control GetControl(int x, int y) {
        Window topWindow = null;
        for (Window window : GetOrderedWindows()) {
            if (window.Contains(x, y)) {
                if (mapWindowToOrder.get(window) != 0) {
                    topWindow = window;
                }
                Control result = GetControlInContainer(window, 
                        x - (window.GetX() + window.GetXOffset()),
                        y - (window.GetY() + window.GetYOffset()));
                if (result == null) {
                    return window;
                } else {
                    return result;
                }
            }
        }
        if (topWindow != null) {
            mapWindowToOrder.remove(topWindow);
            IncrementOrder();
            mapWindowToOrder.put(topWindow, 0);
        }
        return null;
    }

    private Window GetFocusedWindow() {
        Window[] windows = GetOrderedWindows();
        if (windows.length != 0) {
            return windows[0];
        }
        return null;
    }

    private Control GetControlInContainer(Control control, int x, int y) {
        Control result = null;
        for (Control childContainer : mapOwnerToChildren.get(control)) {
            if (childContainer.IsContainer() && childContainer.Contains(x, y)) {
                result = GetControlInContainer(childContainer,
                        x - childContainer.GetX(), y - childContainer.GetY());
                break;
            }
        }
        if (result != null) {
            return result;
        }
        for (Control child : mapOwnerToChildren.get(control)) {
            if (!child.IsContainer() && child.Contains(x, y)) {
                result = child;
                break;
            }
        }
        return result;
    }

    private void IncrementOrder() {
        for (Window window : mapWindowToOrder.keySet()) {
            mapWindowToOrder.put(window, mapWindowToOrder.get(window) + 1);
        }
    }

    public void OnKeyPressed(String keyText, String keyModifiersText) {
        KeyHandle(keyText, keyModifiersText, true);
    }

    public void OnKeyReleased(String keyText, String keyModifiersText) {
        KeyHandle(keyText, keyModifiersText, false);
    }

    private void KeyHandle(String keyText, String keyModifiersText, boolean isPressed) {
        Window window = GetFocusedWindow();
        if (window != null && mapWindowToFocusedControl.containsKey(window)) {
            Control control = mapWindowToFocusedControl.get(window);
            if (control != null) {
                mapControlToClient.get(control).RegisterEvent(
                    control, new KeyEventArgs(keyText, keyModifiersText, isPressed));
            }
        }
    }

    void CloseClient(SingleClient closedClient) {
        List<Control> controlForRemove = new ArrayList();
        for (Control control : mapControlToClient.keySet()) {
            if (mapControlToClient.get(control) == closedClient)
                controlForRemove.add(control);
        }
        for (Control control : controlForRemove) {
            mapControlToClient.remove(control);
            mapIdToControl.remove(control.GetID());
            if (control.getClass() == Window.class) {
                mapWindowToOrder.remove(control);
            } else {
                //TODO add handling of other case here
            }
        }
        board.updateUI();
    }

    void UpdateControl(UpdateMessage updateMessage) {
        String id = updateMessage.GetId();
        Control control = mapIdToControl.get(id);
        String fieldName = updateMessage.GetKey();
        String newValue = updateMessage.GetValue();
        Class controlClass = control.getClass();
        try {
            Method needField = controlClass.getDeclaredMethod(fieldName, String.class);
            needField.setAccessible(true);
            if(needField.getParameterTypes()[0] == String.class)
                needField.invoke(control, newValue);
        } catch (Exception e) {
            
        }
        board.updateUI();
    }
}
